// $URL: http://mingw-lib.googlecode.com/svn/trunk/working/avcodec_to_widget_8/nam.cpp $
// $Rev: 287 $
// $Author: akio.miyoshi $
// $Date:: 2010-07-27 06:46:59 +0000#$

#include "nam.h"
#include "t_httpreply.h"
#include "private/qnoncontiguousbytedevice_p.h"

NetworkAccessManager::NetworkAccessManager(QObject *parent)
    : QNetworkAccessManager(parent)
{
}

void NetworkAccessManager::setBackendFactory(NetworkAccessBackendFactory *a_factory)
{
    m_factory = a_factory;
}

QNetworkReply *NetworkAccessManager::createRequest(
        Operation op,
        const QNetworkRequest & request,
        QIODevice * outgoingData)
{
    qDebug() << "[NetworkAccessManager::createRequest()]" << request.url();
    QObject *backend = 0;
    if(m_factory)
    {
        backend = m_factory->create(this, op, request, outgoingData);
    }
    return QNetworkAccessManager::createRequestWithBackend(op, request, outgoingData, backend);
}

QNetworkReply *NetworkAccessManager::createRequestWithBackend(
        Operation op,
        const QNetworkRequest &request,
        QIODevice *outgoingData,
        QObject *backend)
{
    qDebug() << "[NetworkAccessManager::createRequestWithBackend()]" << request.url();
    if(backend)
    {
        return QNetworkAccessManager::createRequestWithBackend(op, request, outgoingData, backend);
    }
    return this->createRequest(op, request, outgoingData);
}

static QNetworkReply::NetworkError s_networkErrorCodeFromHttpStatus(int httpStatusCode, const QUrl &url);

HttpAccessBackend::HttpAccessBackend(QNetworkAccessManager * nam)
    : QNetworkAccessBackend()
    , m_nam(nam)
    , m_httpReply(0)
    , m_uploadDevice(0)
#ifndef QT_NO_OPENSSL
    , m_pendingSslConfiguration(0), m_pendingIgnoreAllSslErrors(false)
#endif
{
    //QNetworkAccessManagerPrivate *QNetworkAccessBackend::manager;
    this->manager = nam->d_func();
}

HttpAccessBackend::~HttpAccessBackend()
{
    p_disconnectFromHttp();
#ifndef QT_NO_OPENSSL
    delete m_pendingSslConfiguration;
#endif
}

QNetworkAccessManager *HttpAccessBackend::networkAccessManager()
{
    return m_nam;
}

QIODevice *HttpAccessBackend::uploadDevice()
{
    if(m_uploadDevice)
    {
        return m_uploadDevice;
    }
    if(reply->outgoingData || reply->outgoingDataBuffer)
    {
        m_uploadDevice = QNonContiguousByteDeviceFactory::wrap(createUploadByteDevice());
        m_uploadDevice->setParent(this);
    }
    else
    {
        m_uploadDevice = 0;
    }
    return m_uploadDevice;
}

void HttpAccessBackend::setUploadDevice(QIODevice *a_device)
{
    m_uploadDevice = a_device;
}

/* virtual */ void HttpAccessBackend::open()
{
    m_httpReply = this->createHttpReply();
    m_httpReply->setParent(this);
#ifndef QT_NO_OPENSSL
    if (m_pendingSslConfiguration)
        m_httpReply->setSslConfiguration(*m_pendingSslConfiguration);
    if (m_pendingIgnoreAllSslErrors)
        m_httpReply->ignoreSslErrors();
    m_httpReply->ignoreSslErrors(m_pendingIgnoreSslErrorsList);
#endif
    //authenticationRequired()
    connect(m_httpReply,
            SIGNAL(authenticationRequired(QAuthenticator*)),
            SLOT(replyAuthenticationRequired(QAuthenticator*)));
    //readyRead()
    connect(m_httpReply, SIGNAL(readyRead()), SLOT(replyReadyRead()));
    //metaDataChanged()
    connect(m_httpReply, SIGNAL(metaDataChanged()), SLOT(replyMetaDataChanged()));
    //finished()
    connect(m_httpReply, SIGNAL(finished()), SLOT(replyFinished()));
    //error()
    connect(m_httpReply, SIGNAL(error(QNetworkReply::NetworkError)),
            SLOT(replyError(QNetworkReply::NetworkError)));
    //uploadProgress()
    connect(m_httpReply, SIGNAL(uploadProgress(qint64,qint64)), SLOT(replyUploadProgress(qint64,qint64)));
#ifndef QT_NO_OPENSSL
    connect(m_httpReply, SIGNAL(sslErrors(QList<QSslError>)), SLOT(sslErrors(QList<QSslError>)));
#endif
}

/* virtual */ void HttpAccessBackend::downstreamReadyWrite()
{
    //qDebug() << "[HttpAccessBackend::downstreamReadyWrite()]";
    p_readFromHttp();
}

/* virtual */ void HttpAccessBackend::copyFinished(QIODevice *dev)
{
    //TODO: delete this function if possible
    delete dev;
    HttpAccessBackend::finished();
}
/* slot */ void HttpAccessBackend::finished()
{
    //qDebug() << "[HttpAccessBackend::finished()]" << QNetworkAccessBackend::request().url();
    p_disconnectFromHttp();
    QNetworkAccessBackend::finished();
}

/* slot */ void HttpAccessBackend::replyReadyRead()
{
    //qDebug() << "HttpAccessBackend::replyReadyRead()";
    p_readFromHttp();
}

/* slot */ void HttpAccessBackend::replyFinished()
{
    //TODO: delete this line(?)
    // we haven't read everything yet. Wait some more.
#if 0x0
    if (m_httpReply->bytesAvailable()) return;
#else
    if (m_httpReply->bytesAvailable())
    {
        QByteDataBuffer list;
        QByteArray v_bytes = m_httpReply->read(m_httpReply->bytesAvailable());//FIXME readAll()
        list.append(v_bytes);
        //if (!list.isEmpty())
        QNetworkAccessBackend::writeDownstreamData(list);
    }
#endif

    int statusCode = m_httpReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
    if (statusCode >= 400) {
        // it's an error reply
        QString msg = QLatin1String(QT_TRANSLATE_NOOP("QNetworkReply",
                                                      "Error downloading %1 - server replied: %2"));
        msg = msg.arg(url().toString(), m_httpReply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString());
        QNetworkAccessBackend::error(
                s_networkErrorCodeFromHttpStatus(statusCode, url()),
                msg);
    }
    HttpAccessBackend::finished();
}

/* slot */ void HttpAccessBackend::replyMetaDataChanged()
{
    //qDebug() << "HttpAccessBackend::replyMetaDataChanged():" << QNetworkAccessBackend::request().url();
    // HTTP Status
    QNetworkAccessBackend::setAttribute(
            QNetworkRequest::HttpStatusCodeAttribute,
            m_httpReply->attribute(QNetworkRequest::HttpStatusCodeAttribute));
    // HTTP Reason Phrase
    QNetworkAccessBackend::setAttribute(
            QNetworkRequest::HttpReasonPhraseAttribute,
            m_httpReply->attribute(QNetworkRequest::HttpReasonPhraseAttribute));

    // HTTP Response Headers
    QList<QByteArray> v_headerList = m_httpReply->rawHeaderList();
    foreach(QByteArray v_first, v_headerList)
    {
        QByteArray v_second = m_httpReply->rawHeader(v_first);
        //qDebug() << v_first << v_second;
        QNetworkAccessBackend::setRawHeader(v_first, v_second);
    }
    // is it a redirection?
    const int statusCode = m_httpReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
    p_checkForRedirect(statusCode);
    QNetworkAccessBackend::metaDataChanged();
}

/* slot */ void HttpAccessBackend::namAuthenticationRequired(QNetworkReply *reply, QAuthenticator *authenticator)
{
    Q_UNUSED(reply);
    QNetworkAccessBackend::authenticationRequired(authenticator);
}

/* slot */ void HttpAccessBackend::replyAuthenticationRequired(QAuthenticator *authenticator)
{
    //qDebug() << "[HttpAccessBackend::replyAuthenticationRequired()]" << authenticator->realm();
    QNetworkAccessBackend::authenticationRequired(authenticator);
}

/* slot */ void HttpAccessBackend::replyError(QNetworkReply::NetworkError errorCode)
{
    NetworkHttpReply *v_reply = dynamic_cast<NetworkHttpReply *>(QObject::sender());
    QString errorString = v_reply ? v_reply->errorString() : QString::number(errorCode);
    //qDebug() << "[HttpAccessBackend::replyError()]" << errorCode << errorString;
    QNetworkAccessBackend::error(errorCode, errorString);
    HttpAccessBackend::finished();
}

/* slot */ void HttpAccessBackend::replyUploadProgress(qint64 bytesSent, qint64 bytesTotal)
{
    //qDebug() << "[HttpAccessBackend::replyUploadProgress()]" << bytesSent << bytesTotal;
    QNetworkAccessBackend::emitReplyUploadProgress(bytesSent, bytesTotal);
}

/* slot */ void HttpAccessBackend::replyDownloadProgress(qint64 bytesReceived, qint64 bytesTotal)
{
    //TODO: delete this
    //qDebug() << "[HttpAccessBackend::replyDownloadProgress()]" << bytesReceived << bytesTotal;
}

//TODO: delete m_pending...
void HttpAccessBackend::ignoreSslErrors()
{
    if (m_httpReply)
        m_httpReply->ignoreSslErrors();
    else
        m_pendingIgnoreAllSslErrors = true;
}

void HttpAccessBackend::ignoreSslErrors(const QList<QSslError> &errors)
{
    if (m_httpReply)
    {
        m_httpReply->ignoreSslErrors(errors);
    }
    else
    {
        // the pending list is set if QNetworkReply::ignoreSslErrors(const QList<QSslError> &errors)
        // is called before QNetworkAccessManager::get() (or post(), etc.)
        m_pendingIgnoreSslErrorsList = errors;
    }
}

void HttpAccessBackend::fetchSslConfiguration(QSslConfiguration & config) const
{
    if (m_httpReply)
        config = m_httpReply->sslConfiguration();
    else if (m_pendingSslConfiguration)
        config = *m_pendingSslConfiguration;
}

void HttpAccessBackend::setSslConfiguration(const QSslConfiguration & config)
{
    if (m_httpReply)
        m_httpReply->setSslConfiguration(config);
    else if (m_pendingSslConfiguration)
        *m_pendingSslConfiguration = config;
    else
        m_pendingSslConfiguration = new QSslConfiguration(config);
}

void HttpAccessBackend::p_disconnectFromHttp()
{
}

void HttpAccessBackend::p_readFromHttp()
{
    QByteDataBuffer list;
    while (m_httpReply->bytesAvailable() != 0
           && QNetworkAccessBackend::nextDownstreamBlockSize() != 0
           && QNetworkAccessBackend::nextDownstreamBlockSize() > list.byteAmount()) {
        QByteArray v_bytes = m_httpReply->read(QNetworkAccessBackend::nextDownstreamBlockSize() - list.byteAmount());
        list.append(v_bytes);
    }
    if (!list.isEmpty()) QNetworkAccessBackend::writeDownstreamData(list);
}

void HttpAccessBackend::p_checkForRedirect(const int statusCode)
{
    switch (statusCode) {
    case 301:				   // Moved Permanently
    case 302:				   // Found
    case 303:				   // See Other
    case 307:				   // Temporary Redirect
        // What do we do about the caching of the HTML note?
        // The response to a 303 MUST NOT be cached, while the response to
        // all of the others is cacheable if the headers indicate it to be
        QByteArray header = QNetworkAccessBackend::rawHeader("location");
        QUrl url = QUrl::fromEncoded(header);
        if (!url.isValid()) url = QUrl(QLatin1String(header));
        ////p_checkForRedirect_YouTube(url); //TODO: move to T_HttpReply
        QNetworkAccessBackend::redirectionRequested(url);
    }
}

static QNetworkReply::NetworkError s_networkErrorCodeFromHttpStatus(int httpStatusCode, const QUrl &url)
{
    QNetworkReply::NetworkError code;
    // we've got an error
    switch (httpStatusCode) {
    case 401:			   // Authorization required
        code = QNetworkReply::AuthenticationRequiredError;
        break;

    case 403:			   // Access denied
        code = QNetworkReply::ContentOperationNotPermittedError;
        break;

    case 404:			   // Not Found
        code = QNetworkReply::ContentNotFoundError;
        break;

    case 405:			   // Method Not Allowed
        code = QNetworkReply::ContentOperationNotPermittedError;
        break;

    case 407:
        code = QNetworkReply::ProxyAuthenticationRequiredError;
        break;

    default:
        if (httpStatusCode > 500) {
            // some kind of server error
            code = QNetworkReply::ProtocolUnknownError;
        } else if (httpStatusCode >= 400) {
            // content error we did not handle above
            code = QNetworkReply::UnknownContentError;
        } else {
            qWarning("QNetworkAccess: got HTTP status code %d which is not expected from url: \"%s\"",
                     httpStatusCode, qPrintable(url.toString()));
            code = QNetworkReply::ProtocolFailure;
        }
    }

    return code;
}
